#include <rtthread.h>
#include <rthw.h>

rt_inline rt_err_t rt_ipc_object_init(struct rt_ipc_object *ipc)
{
    rt_list_init(&(ipc->suspend_thread));

    return RT_EOK;
}

rt_inline rt_err_t rt_ipc_list_suspend(rt_list_t *list, struct rt_thread *thread, rt_uint8_t flag){
    struct rt_list_node *n;
    struct rt_thread *sthread;

    rt_thread_suspend(thread);

    switch (flag){
        case RT_IPC_FLAG_FIFO:
            rt_list_insert_before(list, &(thread->tlist));
            break;
    
        case RT_IPC_FLAG_PRIO:
            for(n = list->next; n != list; n = n->next){
                sthread = rt_list_entry(n, struct rt_thread, tlist);

                if(thread->current_priority < sthread->current_priority){
                    rt_list_insert_before(&(sthread->tlist), &(thread->tlist));

                    break;
                }
            }
            
            if(n == list)
                rt_list_insert_before(list, &(thread->tlist));

            break;
    }

    return RT_EOK;
}

rt_inline rt_err_t rt_ipc_list_resume(rt_list_t *list){
    struct rt_thread *thread;

    thread = rt_list_entry(list->next, struct rt_thread, tlist);

    rt_thread_resume(thread);

    return RT_EOK;
}

rt_inline rt_err_t rt_ipc_list_resume_all(rt_list_t *list)
{
    struct rt_thread *thread;
    register rt_ubase_t temp;

    while(!rt_list_isempty(list)){
        temp = rt_hw_interrupt_disable();

        thread = rt_list_entry(list->next, struct rt_thread, tlist);

        rt_thread_resume(thread);

        rt_hw_interrupt_enable(temp);
    }

    return RT_EOK;
}

rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag)
{
    rt_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name);

    rt_ipc_object_init(&(sem->parent));

    sem->value = value;

    sem->parent.parent.flag = flag;

    return RT_EOK;
}

rt_err_t rt_sem_detach(rt_sem_t sem)
{
    rt_ipc_list_resume_all(&(sem->parent.suspend_thread));

    rt_object_detach(&(sem->parent.parent));

    return RT_EOK;
}


rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time){
    struct rt_thread *thread;
    register rt_ubase_t temp;

    RT_ASSERT(sem != RT_NULL);
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);

    temp = rt_hw_interrupt_disable();

    if(sem->value > 0){
        sem->value--;

        rt_hw_interrupt_enable(temp);
    }else{
        if(time == 0){
            rt_hw_interrupt_enable(temp);

            return -RT_ETIMEOUT;
        }else{
            thread = rt_thread_self();

            thread->error = RT_EOK;

            rt_ipc_list_suspend(&(sem->parent.suspend_thread), thread, sem->parent.parent.flag);

            if(time > 0){
                rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, time);

                rt_timer_start(&(thread->thread_timer));
            }

            rt_hw_interrupt_enable(temp);

            rt_schedule();

            /* 下次调度此线程，pc指向这里，如果获取到sem，thread->error = RT_EOK */
            if(thread->error != RT_EOK){
                return thread->error;
            }
        }
    }

    return RT_EOK;
}

rt_err_t rt_sem_trytake(rt_sem_t sem){
    return rt_sem_take(sem, 0);
}

rt_err_t rt_sem_release(rt_sem_t sem){
    rt_ubase_t temp;
    rt_bool_t need_schedule;

    RT_ASSERT(sem != RT_NULL);

    need_schedule = RT_FALSE;

    temp = rt_hw_interrupt_disable();

    if(!rt_list_isempty(&(sem->parent.suspend_thread))){
        rt_ipc_list_resume(&(sem->parent.suspend_thread));
        need_schedule = RT_TRUE;
    }else{
        sem->value++;
    }

    rt_hw_interrupt_enable(temp);

    if(need_schedule)
        rt_schedule();

    return RT_EOK;
}

rt_err_t rt_sem_contrl(rt_sem_t sem, int cmd, void *arg){
    rt_ubase_t tmp;

    RT_ASSERT(sem != RT_NULL);

    if(cmd == RT_IPC_CMD_RESET){
        tmp = rt_hw_interrupt_disable();

        rt_ipc_list_resume_all(&(sem->parent.suspend_thread));

        sem->value = (rt_uint16_t)arg;

        rt_hw_interrupt_enable(tmp);

        rt_schedule();

        return RT_EOK;
    }

    return RT_ERROR;
}
